//    PollIT - Tool to make decisions of anything
//    Copyright (C) 2011 Jukka Hell
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

package fi.hell.pollit.domain;

class Poll {

    static hasMany = ["votes":Vote, "options":Opt, "participants":User];

    Date dateCreated;
    Date lastUpdated;

    User creator;
    Calendar validUntil;

    String name;
    String description;

    boolean voteBeforeResults = true;
    boolean publicPoll = false;
    boolean multivote = false;

    static constraints = {
        name            (nullable:false, blank:false);
        description     (nullable:true, blank:false);
        creator         (nullable:false);
        validUntil      (nullable:false);
        participants    (nullable:true);
        votes           (nullable:true);
        options         (minSize:2);
    }
    
    String toString() {
        return name;
    }

    static List<Poll> listAvailable() {
        User u = User.getLoggedInUser();
        GregorianCalendar now = Calendar.getInstance();
        now.set(Calendar.HOUR, 0);
        now.set(Calendar.HOUR_OF_DAY, 0);
        now.set(Calendar.MINUTE, 0);
        now.set(Calendar.SECOND, 0);
        now.set(Calendar.MILLISECOND, 0);

        List<Poll> polls = [];
        polls = Poll.createCriteria().list() {
            or {
                isEmpty("participants")
                eq("publicPoll", true)
            }
            ge("validUntil", now)
        }

        if (u) {
            polls += Poll.createCriteria().list() {
                createAlias("participants", "p")
                eq("p.id", u.id)
                ge("validUntil", now)
            }
        }

        List<Poll> availables = [];
        for (Poll p in polls) {
            if (p.votesNeeded() > 0) {
                availables << p;
            }
        }

        return availables;
    }

    static List<Poll> listClosed() {
        User u = User.getLoggedInUser();
        GregorianCalendar now = Calendar.getInstance();
        now.set(Calendar.HOUR, 0);
        now.set(Calendar.HOUR_OF_DAY, 0);
        now.set(Calendar.MINUTE, 0);
        now.set(Calendar.SECOND, 0);
        now.set(Calendar.MILLISECOND, 0);
        
        List<Poll> polls = [];

        polls = Poll.createCriteria().list() {
            or {
                isEmpty("participants")
                eq("publicPoll", true)
                if (u) {
                    participants {
                        eq("id", u.id)
                    }
                }
            }
        }

        List<Poll> availables = [];
        for (Poll p in polls) {
            if (p.votesNeeded() == 0 || p.validUntil < now) {
                availables << p;
            }
        }

        return availables;
    }

    Map<String, Float> distributions() {
        Map<String, Float> results = [:];
        int totalVotes = votes.size();
        for (Opt o in options) {
            if (totalVotes > 0) {
                int votes = Vote.countByPollAndSelection(this, o);
                results[o] = votes / totalVotes;
            } else {
                results[o] = 0f;
            }
        }

        return results;
    }

    Float distribution(Opt o) {
        int totalVotes = votes.size();
        
        if (totalVotes > 0) {
            int votes = Vote.countByPollAndSelection(this, o);
            return votes / totalVotes;
        } else {
            return 0f;
        }
    }

    boolean isUserVoted(User u, String ip) {
        if (!publicPoll && !u) {
            log.debug("Poll ${this.name} is not public and user not logged in --> Already voted.");
            return true;
        }

        if (u) {
            List<User> voters = votes.voter;
            boolean userAlreadyVoted = voters?.contains(u);

            // If not voted but logged in, then let her vote. No need to check ip's
            if (userAlreadyVoted) {
                log.debug("Poll ${this.name}: Logged in user (${u}) already voted.");
                return true;
            } else {
                return false;
            }
        }

        // If user not logged in and no registered user has this ip
        // We might allow voting
        int usersWithGivenIp = User.countByIp(ip);
        if (!u && usersWithGivenIp == 0) {
            List<String> ips = votes.ip;
            boolean ipAlreadyVoted = ips?.contains(ip);
            if (ipAlreadyVoted) {
                log.debug("Poll ${this.name}: Ip ${ip} already voted and user is not logged in.");
                return true;
            }
        } else if (usersWithGivenIp > 0) {
            log.debug("Poll ${this.name}: Ip ${ip} owned by user(s); user should log in and vote.");
            return true;
        }
    }

    List<Opt> sortedOptions() {
        return this.options.sort{ a,b ->
            int aCount = Vote.countByPollAndSelection(this, a);
            int bCount = Vote.countByPollAndSelection(this, b);
            (aCount == bCount) ? 0 : aCount > bCount ? -1 : 1;
        }
    }

    int votesNeeded() {
        if (publicPoll) {
            return 1;
        }

        int voteCount = Vote.createCriteria().get() {
            eq("poll", this)
            projections {
                countDistinct("voter")
            }
        }

        if (participants.size() > 0) {
            return participants.size() - voteCount;
        } else {
            int userCount = User.count();
            return userCount - voteCount;
        }
    }

    boolean allowedToVote(User u, String ip) {
        boolean userAlreadyVoted = isUserVoted(u, ip);
        if (userAlreadyVoted) {
            log.debug("Not allowed to vote beacause already voted.");
            return false;
        }
        // User/IP combination is not voted so further checks can be made

        // Check if public
        if (publicPoll) {
            return true;
        }

        // Check if participants are set
        if (u && this.participants && this.participants.size() > 0) {
            boolean participant = this.participants.contains(u);
            if (participant) {
                return true;
            } else {
                log.debug("User not allowed to vote ${this.name}: User ${u} not in participants list.");
                return false;
            }
        } else {
            return true;
        }
    }
}
